package eu.ahsx.limboa.media
{
	import com.demonsters.debugger.MonsterDebugger;
	
	import flash.display.Loader;
	import flash.display.MovieClip;
	import flash.errors.EOFError;
	import flash.events.ErrorEvent;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.events.SecurityErrorEvent;
	import flash.net.URLRequest;
	import flash.net.URLStream;
	import flash.system.LoaderContext;
	import flash.utils.ByteArray;
	import flash.utils.Endian;
	
	/***
	 * 	AVM1Loader
	 * 	
	 * 	@author Masy Alexandre
	 * 	@private
	 * */
	internal class AVM1Loader extends EventDispatcher implements IAssetsLoader
	{
		private var l:Loader;
		
		private var file:MediaFile;
		private var debug:Boolean;
		
		private var stream:URLStream;
		
		/**
		 * 
		 * */
		public function AVM1Loader(debug:Boolean = false)
		{
			this.debug = debug;
			super();
		}
		
		/**
		 * 
		 * */
		public function load(file:MediaFile):void
		{
			this.file = file;
			
			if (debug)
				MonsterDebugger.trace("[ASSETS]", file.id + " INIT");
			dispatchEvent(new MediaEvent(file, 0, MediaEvent.INIT));

			l = new Loader();
			l.contentLoaderInfo.addEventListener(Event.COMPLETE, loadComplete, false, 0, true);
			l.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, loadProgress, false, 0, true);
			l.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, loadError, false, 0, true);
			l.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, loadError, false, 0, true);
			
			stream = new URLStream();
			stream.addEventListener(Event.COMPLETE, streamComplete, false, 0, true);
			stream.addEventListener(IOErrorEvent.IO_ERROR, streamComplete, false, 0, true);
			stream.addEventListener(SecurityErrorEvent.SECURITY_ERROR, streamComplete, false, 0, true);
			stream.load(new URLRequest(file.url));
			
			if (debug)
				MonsterDebugger.trace("[ASSETS] ", file.id + " START");
			dispatchEvent(new MediaEvent(file, 0, MediaEvent.START));
		}
		
		public function get percent():int
		{
			if (l)
				return l.contentLoaderInfo.bytesLoaded/l.contentLoaderInfo.bytesTotal;
			else
				return 0;
		}
		
		public function get data():*
		{
			if (l && l.content)
				return l.content as MovieClip;
			else
				return null;
		}
		
		public function stop():void
		{
			if (l)
			{
				l.close();
				l.unload();
			}
			
			if (debug)
				MonsterDebugger.trace("[ASSETS] ", file.id + " ERROR : Stop the loading");
				
			file.info.error = new ErrorEvent(ErrorEvent.ERROR, false, false, "Stop the loading");
	
			dispatchEvent(new MediaEvent(file, 0, MediaEvent.ERROR));
		}
		
		public function unload():void
		{
			this.stop();
			l.unloadAndStop();
		}
		
		private function loadComplete(event:Event):void
		{
			if (debug)
				MonsterDebugger.trace("[ASSETS] ", file.id + " COMPLETE");
			dispatchEvent(new MediaEvent(file, 0, MediaEvent.COMPLETE));
		}
		
		private function loadProgress(event:ProgressEvent):void
		{
			if (debug)
				MonsterDebugger.trace("[ASSETS] ", file.id + " PROGRESS");
			file.info.bytesloaded = event.bytesLoaded;
			file.info.bytesTotal = event.bytesTotal;
			file.info.percent = event.bytesLoaded/event.bytesTotal;
			
			dispatchEvent(new MediaEvent(file, 0, MediaEvent.PROGRESS));
		}
		
		private function loadError(event:ErrorEvent):void
		{
			if (debug)
				MonsterDebugger.trace("[ASSETS] ", file.id + " ERROR :" + event.text);
			
			dispatchEvent(new MediaEvent(file, 0, MediaEvent.ERROR));
			
			file.info.error = event;
			
		}
		
		
		private function streamComplete(event:Event):void
		{
			var inputBytes:ByteArray = new ByteArray();
			stream.readBytes(inputBytes);
			stream.close();
			inputBytes.endian = Endian.LITTLE_ENDIAN;
			
			if (isCompressed(inputBytes)) 
				uncompress(inputBytes);
			
			var version:uint = uint(inputBytes[3]);
			
			if (version < 9) 
				updateVersion(inputBytes, 9);

			if (version > 7)
				flagSWF9Bit(inputBytes);
			else
				insertFileAttributesTag(inputBytes);
			
			var lc:LoaderContext = new LoaderContext();
			if (lc.hasOwnProperty("allowLoadBytesCodeExecution"))
				lc['allowLoadBytesCodeExecution'] = true;
			
			l.loadBytes(inputBytes, lc);
		}
		
		private function isCompressed(bytes:ByteArray):Boolean
		{
			return bytes[0] == 0x43;
		}
		
		private function uncompress(bytes:ByteArray):void
		{
			var cBytes:ByteArray = new ByteArray();
			cBytes.writeBytes(bytes, 8);
			bytes.length = 8;
			bytes.position = 8;
			cBytes.uncompress();
			bytes.writeBytes(cBytes);
			bytes[0] = 0x46;
			cBytes.length = 0;
		}
		
		private function getBodyPosition(bytes:ByteArray):uint
		{
			var result:uint = 0;
			
			result += 3; // FWS/CWS
			result += 1; // version(byte)
			result += 4; // length(32bit-uint)
			
			var rectNBits:uint = bytes[result] >>> 3;
			result += (5 + rectNBits * 4) / 8; // stage(rect)
			
			result += 2;
			
			result += 1; // frameRate(byte)
			result += 2; // totalFrames(16bit-uint)
			
			return result;
		}
		
		private function findFileAttributesPosition(offset:uint, bytes:ByteArray):int
		{
			bytes.position = offset;
			
			try {
				for (;;) {
					var byte:uint = bytes.readShort();
					var tag:uint = byte >>> 6;
					if (tag == 69) {
						return bytes.position - 2;
					}
					var length:uint = byte & 0x3f;
					if (length == 0x3f) {
						length = bytes.readInt();
					}
					bytes.position += length;
				}
			}
			catch (e:EOFError) {
			}
			
			return -1;
		}
		
		private function flagSWF9Bit(bytes:ByteArray):void
		{
			var pos:int = findFileAttributesPosition(getBodyPosition(bytes), bytes);
			if (pos != -1) {
				bytes[pos + 2] |= 0x08;
			}
			else {
				insertFileAttributesTag(bytes);
			}
		}
		
		private function insertFileAttributesTag(bytes:ByteArray):void
		{
			var pos:uint = getBodyPosition(bytes);
			var afterBytes:ByteArray = new ByteArray();
			afterBytes.writeBytes(bytes, pos);
			bytes.length = pos;
			bytes.position = pos;
			bytes.writeByte(0x44);
			bytes.writeByte(0x11);
			bytes.writeByte(0x08);
			bytes.writeByte(0x00);
			bytes.writeByte(0x00);
			bytes.writeByte(0x00);
			bytes.writeBytes(afterBytes);
			afterBytes.length = 0;
		}
		
		private function updateVersion(bytes:ByteArray, version:uint):void
		{
			bytes[3] = version;
		}
	}
}